package com.though.http.soa;

import com.though.http.utils.ConfigParams;
import com.though.http.utils.HttpInvokerConfg;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Map;

public class HttpInvoker {

	private static final Logger logger = LoggerFactory.getLogger(HttpInvoker.class);
	
	private static PoolingHttpClientConnectionManager connManager;
	
	private static CloseableHttpClient httpClient;
	
	private static HttpInvokerConfg invokerConfig = new HttpInvokerConfg();
	
	static {
		ConfigParams params = invokerConfig.getConfigParams();
		
		connManager = new PoolingHttpClientConnectionManager();
		connManager.setMaxTotal(params.getMaxTotal());
		connManager.setDefaultMaxPerRoute(params.getDefaultMaxPerRoute());
		
		Map<HttpRoute, Integer> maxPerRouteMap = params.getMaxPerRouterMap();
		for (Map.Entry<HttpRoute, Integer> entry : maxPerRouteMap.entrySet()) {
			HttpRoute route = entry.getKey();
			Integer max = entry.getValue();
			connManager.setMaxPerRoute(route, max);
		}
		
		httpClient = HttpClients.custom().setUserAgent("Apache-HttpClient").setConnectionManager(connManager).build();
	}
	
	public static HttpResponse execute(HttpRequest request) {
		if (request == null) {
			throw new IllegalArgumentException("http request is not be null");
		}
		
		ConfigParams params = invokerConfig.getConfigParams();
		HttpUriRequest uriRequest = transformRequest(request, params);
		
		logger.info("request:" + uriRequest);
		
		CloseableHttpResponse closeableResponse = null;
		HttpResponse response = null;
		try {
			closeableResponse = httpClient.execute(uriRequest);
			response = transformResponse(closeableResponse);
			return response;
		} 
		catch (IOException e) {
			throw new RuntimeException(e);
		} 
		finally {
			if (closeableResponse != null) {
				try {
					closeableResponse.close();
				} catch (IOException e) {
					logger.error(e.getMessage(), e);
				}
			}
		}
	}
	
	
	private static HttpResponse transformResponse(CloseableHttpResponse closeableResponse) throws IOException {
		HttpResponse response = new HttpResponse();
		int status = closeableResponse.getStatusLine().getStatusCode();
		response.setStatus(status);
		
		HttpEntity entity = closeableResponse.getEntity();
		if (entity != null) {
			byte[] bytes = EntityUtils.toByteArray(entity);
			response.setBytes(bytes);
		}
		return response;
	}


	private static HttpUriRequest transformRequest(HttpRequest request, ConfigParams params) {
		// prepare request
		request.prepare();
		RequestBuilder builder = RequestBuilder.create(request.method().name());
		
		// prepare url parameters
		builder.setUri(request.getFinalUrl());
		
		// prepare post body
		if (request.getFinalEntity() != null) {
			builder.setEntity(request.getFinalEntity());
		}
		
		builder.addHeader("Accept-Encoding", "gzip, deflate");
		builder.addHeader("X-Forwarded-Fox", "");
		if (StringUtils.isNotBlank(params.getReferer())) {
			builder.addHeader("Referer", params.getReferer());
		}
		
		Map<String, String> headerMap = request.getHeaders();
		if(headerMap != null && headerMap.size() > 0) {
			for (Map.Entry<String, String> entry : headerMap.entrySet()) {
				builder.addHeader(entry.getKey(), entry.getValue());
			}
		}
		
		RequestConfig requestConfig = getRequestConfig(request, params);
		builder.setConfig(requestConfig);
		
		return builder.build();
	}


	private static RequestConfig getRequestConfig(HttpRequest request, ConfigParams params) {
		Builder bilder = RequestConfig.custom();
		bilder.setConnectionRequestTimeout(params.getConnectRequestTimeout());
		bilder.setConnectTimeout(params.getConnectTimeout()).setRedirectsEnabled(true);
		bilder.setSocketTimeout(params.getSocketTimeout());

		if (request.getConnectionRequestTimeout() > 0) {
			bilder.setConnectionRequestTimeout(request.getConnectionRequestTimeout());
		}
		if (request.getConnectTimeout() > 0) {
			bilder.setConnectTimeout(request.getConnectTimeout());
		}
		if (request.getSocketTimeout() > 0) {
			bilder.setSocketTimeout(request.getSocketTimeout());
		}
		
		return bilder.build();
	}
}
